﻿using System;
using BoatNGoat.Logic.Map;
using SharpDX;
using SharpDX.Toolkit.Graphics;


namespace BoatNGoat
{
    using Position2 = Vector2;

    class MapView
    {
        private readonly IMap _map;
        private SpriteSheet _tileSet;

        public MapView(IMap map)
        {
            _map = map;
            Camera = new Camera();
        }

        private Tile[,] Tiles {
            get { return _map.Tiles; }
        }

        public SpriteSheet TileSet { 
            get
            {
                return _tileSet;
            }
            set
            {
                _tileSet = value;
                SeaSource = _tileSet.GetSourceRectangle(0, 0);
                SandSource = _tileSet.GetSourceRectangle(0, 1);
                ForestSource = _tileSet.GetSourceRectangle(0, 2);
                MountainSource = _tileSet.GetSourceRectangle(0, 3);
            } 
        }

        public Camera Camera { get; set; }

        /// <summary>
        /// Number of pixels wide a tile is currently drawn.
        /// </summary>
        public float TileWidth
        {
            get
            {
                return (float)(TileSet.SpriteWidth * Camera.Zoom);
            }
        }

        /// <summary>
        /// Number of pixels high a tile is currently drawn.
        /// </summary>
        public float TileHeight
        {
            get
            {
                return (float)(TileSet.SpriteHeight * Camera.Zoom);
            }
        }

        private DrawingRectangle SeaSource { get; set; }
        private DrawingRectangle SandSource { get; set; }
        private DrawingRectangle ForestSource { get; set; }
        private DrawingRectangle MountainSource { get; set; }

        // Centre of the view in map tile coordinates
        private Position2 ViewCentre { get { return Camera.ViewCentre; } }

        private DrawingRectangle GetTile(Tile t)
        {
            if (t == Tile.Water)
                return SeaSource;
            if (t == Tile.Sand)
                return SandSource;
            if (t == Tile.Forest)
                return ForestSource;
            if (t == Tile.Mountain)
                return MountainSource;
            throw new ArgumentOutOfRangeException("t");
        }

        /// <summary>
        /// Get the offset (in screen coordinates) away from the screen centre 
        /// to the place where this map point should be drawn.
        /// </summary>
        /// <param name="mapPosition">The point in map coordinates to draw.</param>
        /// <returns>The offset away from the centre of the screen, in screen coordinates.</returns>
        public Vector2 MapToScreenCentreOffset(Position2 mapPosition)
        {
            Vector2 offset = mapPosition - Camera.ViewCentre;

            return ScreenVector(offset);
        }

        public Vector2 ScreenVector(Vector2 mapVector)
        {
             return new Vector2(
                 x: TileWidth * mapVector.X,
                 y: -TileHeight * mapVector.Y
             );
        }

        /// <summary>
        /// Get the point on the screen where this map position should be drawn.
        /// </summary>
        /// <param name="mapPosition">The map position to be drawn.</param>
        /// <param name="screenCentrePosition">The coordinates in screen space of the centre of the screen.</param>
        /// <returns>The coordinate in screen space where this map position should be drawn.</returns>
        public Vector2 MapToScreen(Position2 mapPosition, Position2 screenCentrePosition)
        {
            return screenCentrePosition + MapToScreenCentreOffset(mapPosition);
        }
        
        /// <summary>
        /// Given a viewport I shall draw unto it,
        //  centred upon Camera.ViewCentre, the portion of the map what will fit.
        /// </summary>
        /// <param name="target">The map is drawn onto this SpriteBatch.</param>
        /// <param name="screenRectangle">Only the portion of the map which fits on this viewport is drawn.</param>
        public void Draw(SpriteBatch target, ViewportF screenRectangle)
        {
            float screenHalfWidth = 0.5f * screenRectangle.Width;
            float screenHalfHeight = 0.5f * screenRectangle.Height;
            float mapHalfWidth = (screenHalfWidth/TileWidth);
            float mapHalfHeight = (screenHalfHeight/TileHeight);

            Position2 screenTopLeft = new Position2(screenRectangle.X, screenRectangle.Y);

            Position2 mapTopLeft = new Position2(
                x: ViewCentre.X - mapHalfWidth,
                y: ViewCentre.Y + mapHalfHeight
            );

            Vector2 tileOffset = new Vector2(
                x: mapTopLeft.X - (float)Math.Floor(mapTopLeft.X),
                y: mapTopLeft.Y - (float)Math.Ceiling(mapTopLeft.Y)
            );

            Vector2 screenOffset = new Vector2(
                x: tileOffset.X*TileWidth,
                y: -tileOffset.Y*TileHeight
            );

            Position2 drawingBegin = (screenTopLeft - screenOffset) + TileSet.Origin;


            Rectangle tilesToDraw = new Rectangle(
                top:    (int) Math.Ceiling(ViewCentre.Y + mapHalfHeight),
                bottom: (int) Math.Floor(ViewCentre.Y - mapHalfHeight),
                left:   (int) Math.Floor(ViewCentre.X - mapHalfWidth),
                right:  (int) Math.Ceiling(ViewCentre.X + mapHalfWidth)
            );

            int yStart = Math.Min(tilesToDraw.Top, _map.Height - 1);
            int yEnd = Math.Max(tilesToDraw.Bottom, 0);
            int xStart = Math.Max(tilesToDraw.Left, 0);
            int xEnd = Math.Min(tilesToDraw.Right, _map.Width - 1);
            
            for (int y = yStart; y >= yEnd; --y)
            {
                for (int x = xStart; x <= xEnd; ++x)
                {
                    DrawingRectangle tileSource = GetTile(Tiles[x, y]);
                    Vector2 pos = new Vector2(
                        y: (tilesToDraw.Top - y - 1) * TileHeight,
                        x: (x - tilesToDraw.Left)* TileWidth
                    );

                    Position2 tilePosition = drawingBegin + pos;

                    target.Draw(TileSet.Texture, tilePosition, tileSource, Color.White, 0.0f,
                                TileSet.Origin, Vector2.One*(float)Camera.Zoom, SpriteEffects.None, 0f);
                }
            }
        }

    }
}
